import sys
import os
import glob
import shutil
import optparse
import distutils.sysconfig
import simpleweb.utils

from simpleweb.admin.plugins import create_project
import simpleweb.admin.plugins.templates.python as pytemplate
import simpleweb.admin.plugins.templates.movable as movtemplate

try:
    import pkg_resources
except ImportError:
    simpleweb.utils.optional_dependency_err('Standalone Deployment', 'setuptools')

def create_movable(name, args, parser):
    '''
The create-movable plugin packs the simpleweb package and simpleweb-admin
tool and includes it with the deployed application.

Optionally it can alos pack all platform independent modules and packages
(modules/packages without C/C++ extensions), and any dependencies that 
are not part of the standard library.

This helps in deployments that are supposed to be contained, for instance
Shared Web Hosts.

arguments: None
'''
    parser.add_option("-l", "--lib-dir", dest="lib_dir", default="site-packages", help="Specifies the directory, where the modules will be copied to. Default is ./site-packages")
    parser.add_option("-c", "--cleanup", action="store_true", default=False, help="Returns project tree to pristine state, before create-movable was run")
    parser.add_option("-a", "--all", action="store_true", default=False, help="Instructs create-movable to also pack all platform independent (pure python) modules and packages which are not in the standard library.")

    option, args = parser.parse_args(args)
    if option.cleanup:
        cleanup_site(os.getcwd(), option.lib_dir)
        create_project.create_file(filename="main.py", template=pytemplate.main, perms=0755)
        os.unlink(os.path.join(os.getcwd(), "simpleweb-admin.py"))
    else:
        cleanup_site(os.getcwd(), option.lib_dir)

        lib_dir = create_libdir(option.lib_dir)

        load_used_modules()
        copy_modules(lib_dir, option.all)

        create_project.create_file(filename="main.py", template=movtemplate.main, perms=0755, lib_dir=option.lib_dir)
        create_project.create_file(filename="simpleweb-admin.py", template=movtemplate.simpleweb_admin, perms=0755, lib_dir=option.lib_dir)

def load_used_modules():
    from simpleweb.app import SimplewebApp
    import simpleweb._urls as urls
    from simpleweb.settings import Config

    #to help make simpleweb runtime modules complete, import these
    from simpleweb.admin import console
    from simpleweb.admin.plugins import create_movable, create_project, create_tables, drop_tables, help, init_db, run, templates
    from simpleweb.plugins import auth, dblayer, runmethods, template
    from simpleweb.extlib import memento, resolver, selector, static, yaro
    from simpleweb import app, dispatcher, middleware, settings, _urls, utils, webserver

    SimplewebApp(urls, Config("config"))

def copy_modules(lib_dir, all=False):
    pure_packages = {}
    pure_modules = {}
    impure = {}
    #now build a list of all the actual module files
    #we're using  currently. This should be ideally called
    #after load_modules
    for name, module in sys.modules.items():
        #some modules are usually imported into other modules
        #for instance, in simpleweb, if you import sys
        #NAME: will show up as simpleweb.sys, but MODULE: will be None
        #in sys.modules. We ignore these.
        if module and hasattr(module, '__file__'):
            dest_path = lib_dir

            if is_filtered(lib_dir, name, module, all=all):
                continue

            #1. first check if its a package, nested module or top-level module
            #2. If a package add to pure_packages
            #3. If a nested module
            #   1. Check if its pure python module
            #   2. If it is, make sure its toplevel package is not listed in impure
            #      because of a previously discovered impure module
            #   3. If its clean. Add it to pure_packages,
            #   4. If it is not clean, add it to impure, and then make sure it is doesn't exist
            #      in pure_packages
            #4. If a normal module
            #   1. Check if its a pure python module
            #   2. If it is, add it to pure_modules
            #   3. If it is not, add it to impure

            if is_package(name, module):
                top_level_name = name.split('.', 2)[0]
                if not impure.has_key(top_level_name):
                    pure_packages[top_level_name] = __import__(top_level_name)
            elif is_nested_module(name, module):
                top_level_name = name.split('.', 2)[0]
                if is_puremodule(name, module):
                    if not impure.has_key(top_level_name):
                        pure_packages[top_level_name] = __import__(top_level_name) #faster to just add and overwrite, than to check first. no?
                else:
                    impure[top_level_name] = 'package'
                    try:
                        del pure_packages[top_level_name]
                    except KeyError:
                        pass
            else:
                if is_puremodule(name, module):
                    pure_modules[name] = module
                else:
                    impure[os.path.split(module.__file__)[1]] = 'module'

    process_packages(pure_packages, lib_dir)
    process_modules(pure_modules, lib_dir)
    process_impure(impure)

def process_packages(packages, lib_dir):
    for k,v in packages.iteritems():
        dest_path = create_pkg_dir(k, lib_dir, dummy_run=True)
        src_path = get_module_file(v, True)
        copy_file(src_path, dest_path)

def process_modules(modules, lib_dir):
    dest_path = lib_dir
    for m in modules.itervalues():
        src_path = get_module_file(m)
        copy_file(src_path, dest_path)

def process_impure(impure):
    for n in impure.keys():
        print "  %s (%s) - Skipped. Not platform independent" % (n, impure[n])

def cleanup_site(root_dir, lib_dir):
    try:
        lib_dir = os.path.join(root_dir, lib_dir)
        shutil.rmtree(lib_dir)
    except OSError, (code, msg):
        if code == 2: #No such file or directory
            pass
        else:
            raise

def create_libdir(lib_dir, mkdirs=True):
    lib_dir = os.path.join(os.getcwd(), lib_dir)

    if mkdirs:
        try:
            os.mkdir(lib_dir)
        except OSError, (code, msg):
            if code == 17: #File Exists.
                #delete it first
                cleanup_site(os.getcwd(), lib_dir)
                pass
            else:
                raise

    return lib_dir

def create_pkg_dir(pkgname, root_dir, is_pkg=True, dummy_run=False):
    if is_pkg:
        rel_path = build_pkg_path(pkgname)
    else:
        rel_path = build_nested_module_path(pkgname)

    path = os.path.join(root_dir, rel_path)

    if not dummy_run:
        try:
            os.makedirs(path)
        except OSError, (code, msg):
            if code == 17:
                #ignore, File Exists
                pass
            else:
                raise
    return path


def build_pkg_path(pkgname):
    #if we have 'a.b.c' as the actual package,
    #then we should have a/b/c
    #so that we'll eventually have a/b/c/__init__.py
    path = pkgname
    return path.replace('.', os.path.sep)

def build_nested_module_path(pkgname):
    #if we have a.b.c, c as the actual module
    #we need to to return a/b not a/b/c
    #so that our result will be:
    # a/b/cmodule.py NOT a/b/c/cmodule.py

    path = pkgname[:pkgname.rfind('.')]
    return path.replace('.', os.path.sep)

def conv_obj2src(filename):
    '''conv_obj2src('afilename.py[c|o]') -> 'afilename.py'

    Converts the extention from python object file (pyc or pyo) to py.
    .py files should stay unchanged.
    '''
    ext = os.path.splitext(filename)[1]
    if ext == '.pyc':
        filename = filename.replace('.pyc', '.py')

    if ext == '.pyo':
        filename = filename.replace('.pyo', '.py')

    if ext == '.so' or ext == '.a' or ext == '.dll':
        #TODO: issue prompt of some sort to let user remember 
        #to install underlying OS libraries that this code *may* 
        #depend on
        pass
    return filename

def get_module_file(module, is_pkg=False):
    module_file_path, module_file  = os.path.split(module.__file__)
    module_file = conv_obj2src(module_file)
    src_path = pkg_resources.resource_filename(module.__name__, module_file)

    if is_pkg:
        #src_path will be /path/to/__init__.py
        #we need /path/to
        path, init_py = os.path.split(src_path)
        return path
    else:
        return src_path

def copy_file(src_path, dst_path):
    if os.path.isdir(src_path):
        shutil.copytree(src_path, dst_path)
    else:
        shutil.copy(src_path, dst_path)


#Filters
def is_filtered(lib_dir, modulename, module, all):
    if is_stdlib(module):
        return True
    elif is_in_libdir(lib_dir, module):
        return True
    elif is_in_current_app(module):
        return True
    elif is_simpleweb_admin(modulename):
        return True
    elif not all:
        if not is_simpleweb_module(modulename):
            return True
    else:
        return False

def is_stdlib(module):
    site_packages_prefix = distutils.sysconfig.get_python_lib()
    stdlib_prefix = os.path.split(site_packages_prefix)[0]
    if not module.__file__.startswith(site_packages_prefix) and module.__file__.startswith(stdlib_prefix):
        return True
    else:
        return False

def is_in_libdir(lib_dir, module):
    if module.__file__.startswith(lib_dir):
        return True
    else:
        return False

def is_in_current_app(module):
    #file path starts with ./path/to/file
    if module.__file__.startswith('.'):
        return True
    else:
        return False

def is_simpleweb_admin(name):
    #since simpleweb-admin is an entry point, its actually
    #imported as a module whose name is '__main__'.
    #for now, we don't need this.
    if name.startswith('__main__'):
        return True
    else:
        return False

def is_simpleweb_module(name):
    if name.startswith('simpleweb'):
        return True
    else:
        return False

def is_package(modulename, module):
    filename = os.path.split(module.__file__)[1]
    if filename.startswith('__init__.py'):
        return True
    else:
        return False

def is_nested_module(modulename, module):
    if modulename.find('.') >= 0:
        return True
    else:
        return False

def is_puremodule(modulename, module):
    if module.__file__.endswith('.py') or module.__file__.endswith('.pyc') or module.__file__.endswith('.pyo'):
        return True
    else:
        return False
